import { convertApiProvidedFilterToClickhouseFilter } from "@/src/features/public-api/server/filter-builder";
import {
  convertToScore,
  StringFilter,
  type ScoreRecordReadType,
  queryClickhouse,
  measureAndReturn,
} from "@langfuse/shared/src/server";

export type ScoreQueryType = {
  page: number;
  limit: number;
  projectId: string;
  traceId?: string;
  userId?: string;
  name?: string;
  source?: string;
  fromTimestamp?: string;
  toTimestamp?: string;
  value?: number;
  scoreId?: string;
  configId?: string;
  queueId?: string;
  traceTags?: string | string[];
  operator?: string;
  scoreIds?: string[];
  dataType?: string;
};

/**
 * @internal
 * Internal utility function for getting scores by ID.
 * Do not use directly - use ScoresApiService or repository functions instead.
 */
export const _handleGenerateScoresForPublicApi = async ({
  props,
  scoreScope,
}: {
  props: ScoreQueryType;
  scoreScope: "traces_only" | "all";
}) => {
  const { scoresFilter, tracesFilter } = generateScoreFilter(props);
  const appliedScoresFilter = scoresFilter.apply();
  const appliedTracesFilter = tracesFilter.apply();

  const query = `
      SELECT
          t.user_id as user_id,
          t.tags as tags,
          t.environment as trace_environment,
          s.id as id,
          s.project_id as project_id,
          s.timestamp as timestamp,
          s.environment as environment,
          s.name as name,
          s.value as value,
          s.string_value as string_value,
          s.author_user_id as author_user_id,
          s.created_at as created_at,
          s.updated_at as updated_at,
          s.source as source,
          s.comment as comment,
          s.metadata as metadata,
          s.data_type as data_type,
          s.config_id as config_id,
          s.queue_id as queue_id,
          s.trace_id as trace_id,
          s.observation_id as observation_id,
          s.session_id as session_id,
          s.dataset_run_id as dataset_run_id
      FROM
          scores s 
          LEFT JOIN __TRACE_TABLE__ t ON s.trace_id = t.id
          AND s.project_id = t.project_id
      WHERE
          s.project_id = {projectId: String}
          AND (
            ${scoreScope === "traces_only" ? "" : "s.trace_id IS NULL OR "}
            (s.trace_id IS NOT NULL AND (t.id, t.project_id) IN (
              SELECT
                trace_id,
                project_id
              FROM
                scores s
              WHERE
                s.project_id = {projectId: String}
                ${appliedScoresFilter.query ? `AND ${appliedScoresFilter.query}` : ""}
                ${scoreScope === "traces_only" ? "AND s.session_id IS NULL AND s.dataset_run_id IS NULL" : ""}
              ORDER BY
                s.timestamp desc
              LIMIT
                1 BY s.id, s.project_id
                ))
          )
          ${scoreScope === "traces_only" ? "AND s.session_id IS NULL AND s.dataset_run_id IS NULL" : ""}
          ${appliedScoresFilter.query ? `AND ${appliedScoresFilter.query}` : ""}
          ${tracesFilter.length() > 0 ? `AND ${appliedTracesFilter.query}` : ""}
      ORDER BY
          s.timestamp desc
      LIMIT
          1 BY s.id, s.project_id
      ${props.limit !== undefined && props.page !== undefined ? `LIMIT {limit: Int32} OFFSET {offset: Int32}` : ""}
      `;

  return measureAndReturn({
    operationName: "_handleGenerateScoresForPublicApi",
    projectId: props.projectId,
    input: {
      params: {
        ...appliedScoresFilter.params,
        ...appliedTracesFilter.params,
        projectId: props.projectId,
        ...(props.limit !== undefined ? { limit: props.limit } : {}),
        ...(props.page !== undefined
          ? { offset: (props.page - 1) * props.limit }
          : {}),
      },
      tags: {
        feature: "scoring",
        type: "score",
        projectId: props.projectId,
        scoreScope,
        operation_name: "_handleGenerateScoresForPublicApi",
      },
    },
    existingExecution: async (input) => {
      const records = await queryClickhouse<
        ScoreRecordReadType & {
          tags: string[];
          user_id: string;
          trace_environment: string;
        }
      >({
        query: query.replace("__TRACE_TABLE__", "traces"),
        params: input.params,
        tags: { ...input.tags, experiment_amt: "original" },
      });

      return records.map((record) => ({
        ...convertToScore(record),
        trace:
          record.trace_id !== null
            ? {
                userId: record.user_id,
                tags: record.tags,
                environment: record.trace_environment,
              }
            : null,
      }));
    },
    newExecution: async (input) => {
      const records = await queryClickhouse<
        ScoreRecordReadType & {
          tags: string[];
          user_id: string;
          trace_environment: string;
        }
      >({
        query: query.replace("__TRACE_TABLE__", "traces_all_amt"),
        params: input.params,
        tags: { ...input.tags, experiment_amt: "new" },
      });

      return records.map((record) => ({
        ...convertToScore(record),
        trace:
          record.trace_id !== null
            ? {
                userId: record.user_id,
                tags: record.tags,
                environment: record.trace_environment,
              }
            : null,
      }));
    },
  });
};

/**
 * @internal
 * Internal utility function for getting scores by ID.
 * Do not use directly - use ScoresApiService or repository functions instead.
 */
export const _handleGetScoresCountForPublicApi = async ({
  props,
  scoreScope,
}: {
  props: ScoreQueryType;
  scoreScope: "traces_only" | "all";
}) => {
  const { scoresFilter, tracesFilter } = generateScoreFilter(props);
  const appliedScoresFilter = scoresFilter.apply();
  const appliedTracesFilter = tracesFilter.apply();

  // for this query, we only need the traces join if we have a filter on traces
  const query = `
      SELECT
        count() as count
      FROM
        scores s 
          LEFT JOIN __TRACE_TABLE__ t ON s.trace_id = t.id
          AND s.project_id = t.project_id
      WHERE
        s.project_id = {projectId: String}
      AND (
        ${scoreScope === "traces_only" ? "" : "s.trace_id IS NULL OR "}
        (s.trace_id IS NOT NULL AND (t.id, t.project_id) IN (
          SELECT
            trace_id,
            project_id
          FROM
            scores s
          WHERE
            s.project_id = {projectId: String}
            ${appliedScoresFilter.query ? `AND ${appliedScoresFilter.query}` : ""}
            ${scoreScope === "traces_only" ? "AND s.session_id IS NULL" : ""}
          ORDER BY
            s.timestamp desc
          LIMIT
            1 BY s.id, s.project_id
        ))
      )
      ${appliedScoresFilter.query ? `AND ${appliedScoresFilter.query}` : ""}
      ${tracesFilter.length() > 0 ? `AND ${appliedTracesFilter.query}` : ""}
      `;

  return measureAndReturn({
    operationName: "_handleGetScoresCountForPublicApi",
    projectId: props.projectId,
    input: {
      params: {
        ...appliedScoresFilter.params,
        ...appliedTracesFilter.params,
        projectId: props.projectId,
      },
      tags: {
        feature: "scoring",
        type: "score",
        projectId: props.projectId,
        scoreScope,
        operation_name: "_handleGetScoresCountForPublicApi",
      },
    },
    existingExecution: async (input) => {
      const records = await queryClickhouse<{ count: string }>({
        query: query.replace("__TRACE_TABLE__", "traces"),
        params: input.params,
        tags: { ...input.tags, experiment_amt: "original" },
      });
      return records.map((record) => Number(record.count)).shift();
    },
    newExecution: async (input) => {
      const records = await queryClickhouse<{ count: string }>({
        query: query.replace("__TRACE_TABLE__", "traces_all_amt"),
        params: input.params,
        tags: { ...input.tags, experiment_amt: "new" },
      });
      return records.map((record) => Number(record.count)).shift();
    },
  });
};

const secureScoreFilterOptions = [
  {
    id: "traceId",
    clickhouseSelect: "trace_id",
    clickhouseTable: "scores",
    filterType: "StringFilter",
    clickhousePrefix: "s",
  },
  {
    id: "name",
    clickhouseSelect: "name",
    clickhouseTable: "scores",
    filterType: "StringFilter",
    clickhousePrefix: "s",
  },
  {
    id: "source",
    clickhouseSelect: "source",
    clickhouseTable: "scores",
    filterType: "StringFilter",
    clickhousePrefix: "s",
  },
  {
    id: "fromTimestamp",
    clickhouseSelect: "timestamp",
    operator: ">=" as const,
    clickhouseTable: "scores",
    filterType: "DateTimeFilter",
    clickhousePrefix: "s",
  },
  {
    id: "toTimestamp",
    clickhouseSelect: "timestamp",
    operator: "<" as const,
    clickhouseTable: "scores",
    filterType: "DateTimeFilter",
    clickhousePrefix: "s",
  },
  {
    id: "value",
    clickhouseSelect: "value",
    clickhouseTable: "scores",
    filterType: "NumberFilter",
    clickhousePrefix: "s",
  },
  {
    id: "scoreIds",
    clickhouseSelect: "id",
    clickhouseTable: "scores",
    filterType: "StringOptionsFilter",
    clickhousePrefix: "s",
  },
  {
    id: "configId",
    clickhouseSelect: "config_id",
    clickhouseTable: "scores",
    filterType: "StringFilter",
    clickhousePrefix: "s",
  },
  {
    id: "queueId",
    clickhouseSelect: "queue_id",
    clickhouseTable: "scores",
    filterType: "StringFilter",
    clickhousePrefix: "s",
  },
  {
    id: "environment",
    clickhouseSelect: "environment",
    clickhouseTable: "scores",
    filterType: "StringOptionsFilter",
    clickhousePrefix: "s",
  },
  {
    id: "dataType",
    clickhouseSelect: "data_type",
    clickhouseTable: "scores",
    filterType: "StringFilter",
    clickhousePrefix: "s",
  },
];

const secureTraceFilterOptions = [
  {
    id: "traceTags",
    clickhouseSelect: "tags",
    clickhouseTable: "traces",
    filterType: "ArrayOptionsFilter",
    clickhousePrefix: "t",
  },
  {
    id: "userId",
    clickhouseSelect: "user_id",
    clickhouseTable: "traces",
    filterType: "StringFilter",
    clickhousePrefix: "t",
  },
  {
    id: "traceEnvironment",
    clickhouseSelect: "environment",
    clickhouseTable: "traces",
    filterType: "StringOptionsFilter",
    clickhousePrefix: "t",
  },
];

const generateScoreFilter = (filter: ScoreQueryType) => {
  const scoresFilter = convertApiProvidedFilterToClickhouseFilter(
    filter,
    secureScoreFilterOptions,
  );
  scoresFilter.push(
    new StringFilter({
      clickhouseTable: "scores",
      field: "project_id",
      operator: "=",
      value: filter.projectId,
    }),
  );

  const tracesFilter = convertApiProvidedFilterToClickhouseFilter(
    filter,
    secureTraceFilterOptions,
  );

  return { scoresFilter, tracesFilter };
};
